import { Route, ResolvedOptions } from './types'
import { pick } from 'lodash-es'
import { createAnyValueStringify, hashString } from './utils'

const resolveImportMode = (filepath: string, options: ResolvedOptions) => {
  const mode = options.importMode
  if (typeof mode === 'function') return mode(filepath)

  for (const pageDir of options.pagesDir) {
    if (
      options.syncIndex &&
      pageDir.baseRoute === '' &&
      new RegExp(`^/${pageDir.dir}/index\\.(${options.extensions.join('|')})$`).test(filepath)
    )
      return 'sync'
  }
  return mode
}

/**
 * Creates a stringified Vue Router route definition.
 */
const stringifyRoutes = (routes: Route[], options: ResolvedOptions) => {
  const imports = new Map<string, string>()

  const stringify = createAnyValueStringify((key, path) => {
    if (key === 'component' && typeof path === 'string') {
      const mode = resolveImportMode(path, options)
      if (mode === 'sync') {
        const importName = hashString(path, 'page_').substring(0, 24)
        const importStr = `import ${importName} from "${path}"`

        imports.set(importName, importStr)
        return importName
      } else {
        return `() => import("${path}")`
      }
    }
  })

  let normalizeRoute: (routes: Route[]) => Route[]

  if (options.isbuild && (!options.keepExtraAttrs || Array.isArray(options.keepExtraAttrs))) {
    const baseKeys = Array.isArray(options.keepExtraAttrs) ? options.keepExtraAttrs : []
    if (options.react) {
      const keys = [
        'key',
        'path',
        'location',
        'component',
        'routes',
        'exact',
        'strict',
        'render',
        ...baseKeys,
      ]
      normalizeRoute = ((routes?: Route[]): Route[] | undefined => {
        if (!routes) return
        return routes.map((obj) => {
          return {
            ...pick(obj, keys),
            routes: normalizeRoute(obj.routes!),
          }
        }) as any
      }) as any
    } else {
      const keys = [
        'name',
        'path',
        'component',
        'props',
        'children',
        'redirect',
        'alias',
        'beforeEnter',
        'meta',
        ...baseKeys,
      ]
      normalizeRoute = ((routes?: Route[]): Route[] | undefined => {
        if (!routes) return
        return routes.map((obj) => {
          return {
            ...pick(obj, keys),
            children: normalizeRoute(obj.children!),
          }
        }) as any
      }) as any
    }
  } else {
    normalizeRoute = (routes) => routes
  }

  const stringRoutes = stringify(routes)

  return {
    imports,
    stringRoutes,
  }
}

export const generateClientRoutesCode = (routes: Route[], options: ResolvedOptions) => {
  const { imports, stringRoutes } = stringifyRoutes(routes, options)

  return [...imports.values(), `const routes = ${stringRoutes}`, 'export default routes'].join(
    ';\n'
  )
}

const findRoutePosition = (
  routes: Route[],
  component: string,
  __position: Route[] = []
): undefined | Route[] => {
  for (const route of routes) {
    if (route.component === component) {
      return [...__position, route]
    } else if (route.children) {
      const result = findRoutePosition(route.children, component, [...__position, route])
      if (result) {
        return result
      }
    }
  }
}

const anyStringify = createAnyValueStringify()

export const generateClientRouteInfo = (routes: Route[], currentComponent: string) => {
  const routePosition = findRoutePosition(routes, currentComponent)

  if (routePosition) {
    const parent: Route | undefined = routePosition[routePosition.length - 2]
    const current: Route = routePosition[routePosition.length - 1]

    const info = {
      parent,
      current,
      ...current,
    }

    return [
      ...Object.entries(info).map(([key, value]) => {
        return `export const ${key} = ${anyStringify(value)}`
      }),
      `export default {${Object.keys(info).join(',')}}`,
    ].join(';\n')
  }
  console.error(`This page is not route page: ${currentComponent}`)
  return `throw new Error('This page is not route page: ${currentComponent}');`
}
